﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Xml;

namespace Nvigorate.Relational.Mapping
{
    [Serializable]
    public class OneToManyRelationship : BaseRelationship, IRelateOneToMany<string>
    {
        protected Dictionary<string, string> _constraints = new Dictionary<string, string>();

        /// <summary>
        /// Defines how the subject and relative relate to one another
        /// </summary>
        public Dictionary<string, string> Constraints
        {
            get { return _constraints; }
        }

        public override bool IsInverse(ITargetRelationship relationship)
        {
            var candidate = relationship as OneToManyRelationship;
            if(candidate == null)
                return false;
            if (
                SubjectType == relationship.RelativeType &&
                RelativeType == relationship.SubjectType &&
                Constraints.All(c => candidate.Constraints[c.Value] == c.Key)
                )
            {
                return true;
            }
            return false;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = _relationship.GetHashCode();
                result = (result * 397) ^ _subject.GetHashCode();
                result = (result * 397) ^ _relative.GetHashCode();
                result = (result * 397) ^ _relativeType.GetHashCode();
                result = (result * 397) ^ _subjectType.GetHashCode();
                result = (result*397) ^ _relativeProperty.GetHashCode();
                return result;
            }
        }

        public override bool Equals(object obj)
        {
            var candidate = obj as OneToManyRelationship;
            if (candidate == null)
                return false;
            return
                Relationship.Equals(candidate.Relationship) &&
                Subject.Equals(candidate.Subject) &&
                SubjectType.Equals(candidate.SubjectType) &&
                Relative.Equals(candidate.Relative) &&
                RelativeType.Equals(candidate.RelativeType) &&
                RelativeProperty.Equals(candidate.RelativeProperty);
        }

        #region ILinqToXml

        public override XElement GetXElementRepresentation()
        {
            return new XElement("relationship",
                                new XAttribute("type", _relationship),
                                new XAttribute("subject", _subject),
                                new XAttribute("relative", _relative),
                                new XAttribute("relativeProperty", _relativeProperty),
                                GetElementForDictionary()
                );
        }

        private List<XElement> GetElementForDictionary()
        {
            return _constraints.Select(kvp =>
                                       new XElement("constraint",
                                                    new XAttribute("subjectField", kvp.Key),
                                                    new XAttribute("relativeField", kvp.Value)
                                           )).ToList();
        }

        public override object ParseXElement(XElement element)
        {
            _relationship = (Relationship)Enum.Parse(typeof(Relationship), element.GetAttributeValue("type") ?? "Parent", true);
            _subject = element.GetAttributeValue("subject");
            _relative = element.GetAttributeValue("relative");
            _relativeProperty = element.GetAttributeValue("relativeProperty");

            _constraints = element.Elements().Select(e =>
                {
                    var subject = e.GetAttributeValue("subjectField");
                    var relative = e.GetAttributeValue("relativeField");
                    return new KeyValuePair<string, string>(subject, relative);
                }).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

            return this;
        }

        #endregion
    }

    public enum RelationPairType
    {
        Subject,
        Relative
    }

    [Serializable]
    public class RelationContainer : ILinqToXml
    {
        public string ContainerName { get; set; }
        public List<RelationPair> SubjectPairs { get; set; }
        public List<RelationPair> RelativePairs { get; set; }

        public XElement GetXElementRepresentation()
        {
            return new XElement("relationTable", 
                                new XAttribute("name", ContainerName),
                                SubjectPairs.Select(p => p.GetXElementRepresentation()),
                                RelativePairs.Select(p => p.GetXElementRepresentation())
                );
        }

        public object ParseXElement(XElement element)
        {
            ContainerName = element.GetAttributeValue("name");
            SubjectPairs =
                element.Elements("parentAssignment").Select(e => new RelationPair().ParseXElement(e)).Cast<RelationPair>().ToList();
            RelativePairs =
                element.Elements("childAssignment").Select(e => new RelationPair().ParseXElement(e)).Cast<RelationPair>().ToList();
            return this;
        }
    }

    [Serializable]
    public class RelationPair : ILinqToXml
    {
        public RelationPairType PairType { get; set; }
        public string  Target { get; set; }
        public string Source { get; set; }

        public XElement GetXElementRepresentation()
        {
            return new XElement(PairType.Equals(RelationPairType.Subject) ? "parentAssignment" : "childAssignment", 
                                new XAttribute("target", Target),
                                new XAttribute("source", Source)
                );
        }

        public object ParseXElement(XElement element)
        {
            PairType = element.Name == "parentAssignment" ? RelationPairType.Subject : RelationPairType.Relative;
            Target = element.GetAttributeValue("target");
            Source = element.GetAttributeValue("source");
            return this;
        }
    }

    [Serializable]
    public class ManyToManyRelationship : BaseRelationship, IRelateManyToMany
    {
        public RelationContainer Container { get; set; }

        public override bool IsInverse(ITargetRelationship relationship)
        {
            var candidate = relationship as ManyToManyRelationship;
            if (candidate == null)
                return false;
            if (
                SubjectType == relationship.RelativeType &&
                RelativeType == relationship.SubjectType &&
                Container.ContainerName == candidate.Container.ContainerName
                )
            {
                return true;
            }
            return false;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = _relationship.GetHashCode();
                result = (result * 397) ^ _subject.GetHashCode();
                result = (result * 397) ^ _relative.GetHashCode();
                result = (result * 397) ^ _relativeType.GetHashCode();
                result = (result * 397) ^ _subjectType.GetHashCode();
                result = (result * 397) ^ _relativeProperty.GetHashCode();
                return result;
            }
        }

        public override bool Equals(object obj)
        {
            var candidate = obj as OneToManyRelationship;
            if(candidate == null)
                return false;
            return
                Relationship.Equals(candidate.Relationship) &&
                Subject.Equals(candidate.Subject) &&
                SubjectType.Equals(candidate.SubjectType) &&
                Relative.Equals(candidate.Relative) &&
                RelativeType.Equals(candidate.RelativeType) &&
                RelativeProperty.Equals(candidate.RelativeProperty);
        }

        #region ILinqToXml

        public override XElement GetXElementRepresentation()
        {
            return new XElement("relationship",
                                new XAttribute("type", _relationship),
                                new XAttribute("subject", _subject),
                                new XAttribute("relative", _relative),
                                new XAttribute("relativeProperty", _relativeProperty),
                                Container.GetXElementRepresentation()
                );
        }

        public override object ParseXElement(XElement element)
        {
            _relationship = (Relationship)Enum.Parse(typeof(Relationship), element.GetAttributeValue("type") ?? "Parent", true);
            _subject = element.GetAttributeValue("subject");
            _relative = element.GetAttributeValue("relative");
            _relativeProperty = element.GetAttributeValue("relativeProperty");
            Container = (RelationContainer) new RelationContainer().ParseXElement(element.Element("relationTable"));
            return this;
        }

        #endregion
    }
}